package ex1;

/**
 * ParallelGameOfLife implements GameOfLife, Master.
 * We implement Master so that workers will be able to notify us upon completion.
 */
public class ParallelGameOfLife implements GameOfLife, Master {
	
	Worker[][] workers;
	int liveWorkers;
	
	/**
	 * We create the working threads, initialize them, and wait until they are all done.
	 * This is basically it, for further info please dig deeper.
	 * 
	 * @see ex1.GameOfLife#invoke(boolean[][], int, int, int)
	 */
	public boolean[][] invoke(boolean[][] initalField, int hSplit, int vSplit,
			int generations) {
		
		initWorkers(initalField, hSplit, vSplit, generations);
		liveWorkers = workers.length * workers[0].length;
		
		for (Worker[] wline : workers)
			for (Worker w : wline)
				w.start();
		
		synchronized (this) {
			while (liveWorkers > 0)
				try { wait(); } 
				catch (InterruptedException e) { e.printStackTrace();}
		}
		
		return getResults(initalField[0].length, initalField.length);
	}
	
	/**
	 * This function is called by the workers (sort of a callback actually), to notify upon completion.
	 * Before reducing worker count, the this-lock is accquired.
	 * If all workers are done, we shall notify the awaiting main thread.
	 */
	public void notifyDone () {
		synchronized(this) {
			liveWorkers--;
			if (liveWorkers <= 0)
				notify();
		}
	}
	
	/**
	 * Retrieves results.
	 * 
	 * @param width width of the result array. (equals to that of the initial field)
	 * @param height height of the result array. (equals to that of the initial field)
	 * @return the final game field (after the desired amount of generations).
	 */
	protected boolean[][] getResults (int width, int height) {
		boolean res[][] = new boolean[height][];
		for (int i = 0; i<res.length ; i++)
			res[i] = new boolean[width];
		
		int w = (int) Math.floor(width /  workers[0].length);
		int h = (int) Math.floor(height / workers.length);
		for (int row = 0 ; row<workers.length ; row++)
			for (int col = 0 ; col<workers[row].length ; col++)
				workers[row][col].fillData(res, row*h, col*w);
		
		return res;
	}
	
	/**
	 * Initilizes worker objects.
	 * 
	 * @param field initial game field.
	 * @param hSplit horizontal split.
	 * @param vSplit vertical split.
	 * @param maxGen desired maximal generation.
	 * 
	 * @see GameOfLife#invoke(boolean[][], int, int, int) regarding hSplit/vSplit.
	 */
	protected void initWorkers (boolean[][] field, int hSplit, int vSplit, int maxGen) {
		int width  = (int) Math.floor(field[0].length / hSplit);
		int height = (int) Math.floor(field.length / vSplit);
		workers = new Worker[vSplit][];
		for (int i = 0 ; i<workers.length ; i++) {
			workers[i] = new Worker[hSplit];
			for (int j = 0 ; j<workers[i].length ; j++) {
				int w = width, h = height;
				if (i == workers.length-1)
					h += field.length - vSplit*height;
				if (j == workers[i].length-1)
					w += field[0].length - hSplit*width;
				workers[i][j] = new Worker (field, i*height, j*width, w, h, maxGen, this);
			}	
		}
		linkWorkers();
	}
	
	/**
	 * Invoked by initWorkers.
	 * Used for initializing the workers with their proper cell neighbour relations.
	 */
	protected void linkWorkers () {
		for (int row = 0 ; row < workers.length ; row++)
			for (int col = 0 ; col < workers[row].length ; col++) {
				Worker tl, tm, tr, l, r, bl, bm, br;
				try { tl = workers[row-1][col-1]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { tl = null; }
				try { tm = workers[row-1][col]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { tm = null; }
				try { tr = workers[row-1][col+1]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { tr = null; }
				try { l = workers[row][col-1]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { l = null; }
				try { r = workers[row][col+1]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { r = null; }
				try { bl = workers[row+1][col-1]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { bl = null; }
				try { bm = workers[row+1][col]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { bm = null; }
				try { br = workers[row+1][col+1]; }
				catch (java.lang.ArrayIndexOutOfBoundsException a) { br = null; }
				
				workers[row][col].init(tl, tm, tr, l, r, bl, bm, br);
			}
	}

}
